package com.atguigu.gmall.order.receiver;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.constant.RabbitConst;
import com.atguigu.gmall.common.service.RabbitService;
import com.atguigu.gmall.model.enums.ProcessStatus;
import com.atguigu.gmall.model.order.OrderInfo;
import com.atguigu.gmall.model.payment.PaymentInfo;
import com.atguigu.gmall.order.service.api.OrderService;
import com.atguigu.gmall.pay.PaymentFeignClient;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.Map;

/**
 * @author: 封捷
 * @create-date: 2022/9/12 16:08
 */
@Slf4j
@Component
public class OrderReceiver {

    @Autowired
    private OrderService orderService;

    @Autowired
    private PaymentFeignClient paymentFeignClient;

    @Autowired
    private RabbitService rabbitService;

    /**
     * 接收延迟消息，检查订单，把超时未支付的订单关闭
     * @param orderId
     * @param message
     * @param channel
     */
    @RabbitListener(queues = RabbitConst.QUEUE_ORDER_CANCEL)
    public void closeOrder(Long orderId, Message message, Channel channel) throws IOException {
        // 1、根据 orderId 获取 OrderInfo 对象
        OrderInfo orderInfo = orderService.getOrderInfoById(orderId);

        if (orderInfo != null) {
            // 2、获取订单状态
            String orderStatus = orderInfo.getOrderStatus();

            // 3、获取订单处理进程的状态
            String processStatus = orderInfo.getProcessStatus();

            // 4、检查上面两个状态是否为未支付
            if ("UNPAID".equals(orderStatus) && "UNPAID".equals(processStatus)) {
                orderService.updateOrderStatus(orderId, ProcessStatus.CLOSED);
            }

            // ※附加功能：查询支付宝交易记录
            Boolean queryResult = paymentFeignClient.queryAliPayTradeExists(orderInfo.getOutTradeNo()).getData();

            if (queryResult) {
                // 交易记录存在，尝试关闭支付宝平台上的交易记录
                Boolean closeResult = paymentFeignClient.closeAliPayTrade(orderInfo.getOutTradeNo()).getData();

                if (closeResult) {
                    // 如果支付宝平台交易记录关闭成功，说明用户确实尚未支付
                    // 通过消息队列发送消息，实现 PaymentInfo 状态的关闭
                    rabbitService.sendMessage(RabbitConst.EXCHANGE_DIRECT_PAYMENT_CLOSE, RabbitConst.ROUTING_PAYMENT_CLOSE, orderId);
                } else {
                    // 如果支付宝平台交易记录关闭失败，说明用户已经支付
                    // 通过消息队列发送消息，把 PaymentInfo 状态改成已支付（如果觉得支付宝异步回调操作可靠，也可以不做这个操作）

                    // 这里再更新一下订单的状态，是为了防止一个几率非常低的情况：触发延迟队列之后用户才完成支付
                    orderService.updateOrderStatus(orderId, ProcessStatus.PAID);
                }

            } else {
                // 交易记录不存在
                PaymentInfo paymentInfo = paymentFeignClient.getPaymentInfoByOutTradeNo(orderInfo.getOutTradeNo()).getData();

                if (paymentInfo != null) {
                    // 如果交易记录不存在，但是 PaymentInfo 存在，
                    // 说明用户打开了二维码页面，但是没有扫码，
                    // 此时在延迟队列中就应该关闭 PaymentInfo
                    // 通过消息队列发送消息，实现 PaymentInfo 状态的关闭
                    rabbitService.sendMessage(RabbitConst.EXCHANGE_DIRECT_PAYMENT_CLOSE, RabbitConst.ROUTING_PAYMENT_CLOSE, orderId);
                }

            }

        }

        // 5、返回手动确认消息
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }

    /**
     * 这个方法接收消息队列的消息：负责把订单状态改为已支付
     * ※说明：@RabbitListener 注解中为什么有两种不同写法？
     *      写法一：@RabbitListener(queues = RabbitConst.QUEUE_ORDER_CANCEL)
     *              这是因为已经在配置类中完成的绑定，这里指定监听的队列即可
     *      写法二：当前方法的写法，在当前 @RabbitListener 注解中执行绑定
     *              这是因为我们没有提供配置类来完成绑定，所以在注解中配置绑定关系
     * @param orderId
     * @param message
     * @param channel
     */
    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange(value = RabbitConst.EXCHANGE_DIRECT_PAYMENT_PAY, durable = "true"),
            value = @Queue(value = RabbitConst.QUEUE_PAYMENT_PAY, durable = "true"),
            key = {RabbitConst.ROUTING_PAYMENT_PAY}
    ))
    public void updateOrderStatusToPaid(Long orderId, Message message, Channel channel) throws IOException {

        try {
            // 判空保护
            if (orderId != null) {
                // 核心操作：修改订单状态
                orderService.updateOrderStatus(orderId, ProcessStatus.PAID);

                // 附加操作：给库存系统发消息，告诉库存系统减库存
                // 通知库存系统发货的总方法
                orderService.notifyWareSystemToDeliver(orderId);

            }

            // 手动返回确认消息
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (IOException e) {
            e.printStackTrace();

            // 消息处理失败，我们就先获取消息是否重复投递
            Boolean redelivered = message.getMessageProperties().getRedelivered();

            if (redelivered) {
                // 如果是重复投递，那么就拒绝这个消息
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);

                // 记录日志
                log.info("消息处理失败，已经重新投递一次，又处理失败，拒绝消息，不再重试。记录日志，人工干预。");

            } else {
                // 如果不是重复投递，那么就重试一次（把消息重新放回消息队列）
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);

                // 记录日志
                log.info("消息处理失败，重新投递一次，表示重试一次。记录日志，用于以后查询系统故障时参考。");
            }

        }

    }

    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange(value = RabbitConst.EXCHANGE_DIRECT_WARE_ORDER),
            value = @Queue(value = RabbitConst.QUEUE_WARE_ORDER),
            key = {RabbitConst.ROUTING_WARE_ORDER}
    ))
    public void updateOrderStatusToWaitingDeliver(String jsonMessage, Message message, Channel channel) throws IOException {

        // 核心操作：根据库存系统的通知决定如何修改订单状态
        // 1、把 JSON 字符串类型的通知消息转换为 Map 类型
        Map<String, Object> messageMap = JSON.parseObject(jsonMessage, Map.class);

        // 2、从 Map 对象中获取数据
        Long orderId = Long.parseLong((String)messageMap.get("orderId"));
        String status = (String) messageMap.get("status");

        // 3、检查 status 判断减库存操作是否成功
        if ("DEDUCTED".equals(status)) {
            // 4、减仓库成功，修改订单状态
            orderService.updateOrderStatus(orderId, ProcessStatus.WAITING_DELEVER);
        } else {
            // 5、减库存失败，修改订单状态
            orderService.updateOrderStatus(orderId, ProcessStatus.STOCK_EXCEPTION);
        }

        // 6、手动返回确认消息
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);

    }

}
